﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Net;

namespace CoffeeChat.Server
{
    public class Program
    {
        public static void Main(string[] args)
        {
            bool isRunning = true;
            ChatServer j = null;
            try
            {
                j = new ChatServer();
                string typing = string.Empty;
                ConsoleKeyInfo lastKey;
                string paramaters;
                while ( isRunning )
                {
                    Console.Title = "Coffee Chat Server - " + typing;
                    lastKey = Console.ReadKey( true );
                    if ( lastKey.Key == ConsoleKey.Enter )
                    {
                        paramaters = string.Empty;
                        if ( typing.IndexOf( ' ' ) > -1 )
                        {
                            paramaters = typing.Remove( 0, typing.IndexOf( ' ' ) + 1 ).Trim();
                            typing = typing.Remove( typing.Length - paramaters.Length ).Trim();
                        }
                        else
                        {
                            typing = typing.Trim();
                        }
                        Console.WriteLine( "ADMIN --> " + typing + "   " + paramaters );
                        switch ( typing )
                        {
                            case "help":
                                if ( paramaters == string.Empty )
                                {
                                    Console.WriteLine( "You may use the following commands.. help, close, save" );
                                }
                                else
                                {
                                    switch ( paramaters )
                                    {
                                        case "help":
                                            Console.WriteLine( "[help] -> Shows a list of commands, or provides help for one command." );
                                            break;
                                        case "close":
                                            Console.WriteLine( "[close] -> Save the account information, then closes the server." );
                                            break;
                                        case "save":
                                            Console.WriteLine( "[save] -> Saves the account information." );
                                            break;
                                        default:
                                            Console.WriteLine( "[" + paramaters + "] -> The command doesn't exist." );
                                            break;
                                    }
                                }
                                break;

                            case "close":
                                isRunning = false;
                                break;

                            case "save":
                                j.Save();
                                Console.WriteLine( "Saved!" );
                                break;

                            default:
                                Console.WriteLine( "Unknown command!" );
                                break;

                        }
                        Console.WriteLine();
                        typing = string.Empty;
                    }
                    else
                    {
                        if ( lastKey.Key == ConsoleKey.Backspace )
                        {
                            if ( typing.Length > 0 )
                            {
                                typing = typing.Substring( 0, typing.Length - 1 );
                            }
                        }
                        else
                        {
                            typing += lastKey.KeyChar;
                        }
                    }
                }
            }
            finally
            {
                Console.WriteLine( "Stopping server..." );
                if ( j != null )
                {
                    j.Save();
                    j.Stop();
                    j.Dispose();
                }
                Console.WriteLine( "Press any key to continue." );
                Console.ReadKey( true );
            }
        }
    }

    public class ChatServer : IDisposable
    {
        private List<ChatRoom> chatRooms;
        private List<ChatClient> people;
        private EasyServer tcp;

        public ChatServer()
        {
            this.chatRooms = new List<ChatRoom>();
            this.people = new List<ChatClient>();
            this.tcp = new EasyServer( 5440 );
            this.Load();

            this.tcp.ConnectionClosed += tcp_ConnectionClosed;
            this.tcp.DataReceived += tcp_DataReceived;
            this.tcp.ErrorFound += tcp_ErrorEncounter;
            this.tcp.ClientConnected += tcp_onConnection;
            this.tcp.ClientDisconnected += tcp_lostConnection;
            tcp.StartConnection();
            Console.WriteLine( "Bound to port {0}! Waiting for connections.", 5440 );
        }
        public void Stop()
        {
            tcp.CloseConnection();
        }
        public void Load()
        {
            byte[] bin = null;
            try
            {
                bin = File.ReadAllBytes( "data_people.dat" );
            }
            catch ( FileNotFoundException )
            {
                Console.WriteLine( "Unable to load [people] from the file, data_people.dat!" );
            }
            catch ( AccessViolationException )
            {
                Console.WriteLine( "Unable to load [people] from the file, data_people.dat! Permission denied!" );
            }
            if ( bin != null && bin.Length > 0 )
            {
                string[] data = Command.CurrentEncoding.GetString( bin ).Split( '*' );
                ChatClient tempPerson;
                for ( int i = 0; i < data.Length; i++ )
                {
                    tempPerson = ChatClient.FromEncryptedData( data[i] );
                    if ( tempPerson != null )
                    {
                        this.people.Add( tempPerson );
                    }
                }
            }
        }
        public void Save()
        {
            string bin = string.Empty;
            ChatClient person;
            for ( int i = 0; i < this.people.Count; i++ )
            {
                person = this.people[i];
                if ( person != null )
                {
                    if ( i > 0 )
                    {
                        bin += "*";
                    }
                    bin += person.ToEncryptedData();
                }
            }
            try
            {
                File.WriteAllBytes( "data_people.dat", Command.CurrentEncoding.GetBytes( bin ) );
            }
            catch ( FileNotFoundException )
            {
                Console.WriteLine( "Unable to save [people] to the file data_people.dat!" );
            }
            catch ( AccessViolationException )
            {
                Console.WriteLine( "Unable to save [people] to the file, data_people.dat! Permission denied." );
            }
        }

        public ChatRoom GetChatRoom(string name)
        {
            if ( name == null )
            {
                name = string.Empty;
            }
            name = name.Trim();
            for ( int i = 0; i < this.chatRooms.Count; i++ )
            {
                if ( name.Equals( this.chatRooms[i].Name, StringComparison.Ordinal ) )
                {
                    return this.chatRooms[i];
                }
            }
            return null;
        }

        public bool IsUserOnline(string user)
        {
            if ( string.IsNullOrEmpty( user ) )
            {
                return false;
            }
            user = user.Trim();
            for ( int i = 0; i < this.people.Count; i++ )
            {
                if ( string.Equals( this.people[i].UserName.Trim(), user ) )
                {
                    return this.people[i].Id != null;
                }
            }
            return false;
        }

        public ChatClient GetUser(string user)
        {
            if ( string.IsNullOrEmpty( user ) )
            {
                return null;
            }
            user = user.Trim();
            for ( int i = 0; i < this.people.Count; i++ )
            {
                if ( string.Equals( this.people[i].UserName.Trim(), user ) )
                {
                    return this.people[i];
                }
            }
            return null;
        }
        public ChatClient GetUserFromId(EndPoint id)
        {
            if ( id == null )
            {
                return null;
            }
            for ( int i = 0; i < this.people.Count; i++ )
            {
                if ( id.Equals( this.people[i].Id ) )
                {
                    return this.people[i];
                }
            }
            return null;
        }

        public void UpdateAllFriends()
        {
            ChatClient tempUser;
            Command tempCmd;
            for ( int i = this.people.Count - 1; i >= 0; i-- )
            {
                tempUser = this.people[i];
                if ( tempUser.Id != null )
                {
                    tempCmd = new Command( 2 );
                    tempCmd.Intent = 110; //Allow Get Friends;
                    for ( int fr = 0; fr < tempUser.Friends.Count; fr++ )
                    {
                        if ( this.IsUserOnline( tempUser.Friends[fr] ) )
                            tempCmd.Add( "online", tempUser.Friends[fr] );
                        else
                            tempCmd.Add( "offline", tempUser.Friends[fr] );
                    }
                    tcp.SendData( tempUser.Id, tempCmd );
                }
            }
        }

        private void tcp_ConnectionClosed()
        {
            Console.WriteLine( "Server closed!" );
        }
        private void tcp_DataReceived(EndPoint id, Command cmd)
        {
            Console.WriteLine( "Data received! Intent = " + cmd.Intent + ", Date = " + DateTime.Now.ToString() );
            ChatClient user;
            Command cmdBuffer = new Command( 16 );
            switch ( cmd.Intent )
            {
                //TODO: Should I reply back to the ping?
                //case 1: //Client is trying to ping the server.
                //    cmdBuffer.Intent = 2;
                //    this.tcp.SendData(id, cmdBuffer);
                //    break;

                //case 2: //Pinging the client was successful.
                //    break;

                case 4: //Login
                    user = GetUser( cmd.GetItem( "user" ) );
                    if ( user != null && !string.IsNullOrEmpty( user.UserName ) )
                    {
                        if ( user.IsLoggedIn() & !object.Equals( user.Id, id ) )
                        {
                            cmdBuffer.Intent = 104; //Deny Login : Multiple Sessions
                        }
                        else
                        {
                            if ( user.TestPassword( cmd.GetItem( "pass" ) ) )
                            {
                                user.Id = id; //Create the session for [id];
                                cmdBuffer.Intent = 102; //Allow login.
                                cmdBuffer.Add( "user", user.UserName );

                                UpdateAllFriends();
                            }
                            else
                            {
                                user.Id = null;
                                cmdBuffer.Intent = 103; //Deny Login : Invalid
                            }
                        }
                    }
                    else
                    {
                        cmdBuffer.Intent = 103; //Deny Login : Invalid
                    }
                    tcp.SendData( id, cmdBuffer );
                    break;

                case 5: //Register
                    user = GetUser( cmd.GetItem( "user" ) );
                    if ( user == null )
                    {
                        user = new ChatClient();
                        user.UserName = cmd.GetItem( "user" );
                        user.SetPassword( cmd.GetItem( "pass" ) );
                        this.people.Add( user );

                        cmdBuffer.Intent = 105; //Allow Register
                        cmdBuffer.Add( "user", user.UserName );
                    }
                    else
                    {
                        cmdBuffer.Intent = 106; //Deny Register
                        cmdBuffer.Add( "user", user.UserName );
                    }
                    tcp.SendData( id, cmdBuffer );
                    break;

                case 6: //Logout/Disconnect
                    user = this.GetUserFromId( id );
                    this.tcp.DisconnectClient( id );
                    if ( user != null )
                    {
                        user.Id = null;
                        UpdateAllFriends();
                    }
                    break;

                case 10: //Get Friends
                    user = this.GetUserFromId( id );
                    if ( user != null )
                    {
                        cmdBuffer.Intent = 110; //Allow Get Friends
                        for ( int i = 0; i < user.Friends.Count; i++ )
                        {
                            if ( this.IsUserOnline( user.Friends[i] ) )
                            {
                                cmdBuffer.Add( "online", user.Friends[i] );
                            }
                            else
                            {
                                cmdBuffer.Add( "offline", user.Friends[i] );
                            }
                        }
                        tcp.SendData( user.Id, cmdBuffer );
                    }
                    break;

                case 11: //Add Friend
                    ChatClient senderUser = this.GetUserFromId( id );
                    ChatClient friendRequest = this.GetUser( cmd.GetItem( "friend" ) );
                    cmdBuffer.Add( "friendreq", cmd.GetItem( "friend" ) );
                    if ( senderUser != null )
                    {
                        if ( friendRequest == null )
                        {
                            cmdBuffer.Intent = 113; //User doesn't exist.
                        }
                        else
                        {
                            if ( !senderUser.HasFriend( friendRequest.UserName ) )
                            {
                                senderUser.Friends.Add( friendRequest.UserName );
                            }
                            cmdBuffer.Intent = 112; //Added friend.
                        }
                        tcp.SendData( id, cmdBuffer );

                        UpdateAllFriends();
                    }
                    else
                    {
                        cmdBuffer.Intent = 114; //No session!
                        tcp.SendData( id, cmdBuffer );
                    }
                    break;

                case 20: //Send Chat Message
                    ChatClient userFrom = this.GetUserFromId( id );
                    string to = cmd.GetItem( "to" );
                    ChatClient userTo = this.GetUser( to );

                    byte[] msg = cmd.ExtraData;
                    string type = cmd.GetItem( "type" );

                    if ( userFrom != null )
                    {
                        if ( !string.IsNullOrEmpty( to ) )
                        {
                            if ( userTo.IsLoggedIn() )
                            {
                                cmdBuffer.Intent = 120; //Allow Send Chat Message
                                cmdBuffer.Add( "to", to );
                                cmdBuffer.Add( "type", type );
                                cmdBuffer.ExtraData = msg;
                                tcp.SendData( id, cmdBuffer );

                                cmdBuffer = new Command( 3 );
                                cmdBuffer.Intent = 120; //Get Chat Message
                                cmdBuffer.Add( "from", userFrom.UserName );
                                cmdBuffer.Add( "type", type );
                                cmdBuffer.ExtraData = msg;
                                tcp.SendData( userTo.Id, cmdBuffer );
                            }
                            else
                            {
                                cmdBuffer.Intent = 123; //Deny Send Chat Message : User not logged in.
                                cmdBuffer.Add( "to", to );
                                tcp.SendData( id, cmdBuffer );
                            }
                        }
                        else
                        {
                            cmdBuffer.Intent = 122; //User doesn't exist.
                            cmdBuffer.Add( "to", to );
                            tcp.SendData( id, cmdBuffer );
                        }
                    }
                    else
                    {
                        cmdBuffer.Intent = 124; //No session!
                        tcp.SendData( id, cmdBuffer );
                    }
                    break;

                case 30: //Send Channel Message
                    ChatClient userSending = this.GetUserFromId( id );
                    if ( userSending != null )
                    {
                        cmdBuffer.Intent = 130; //Allow Send Message to Channel
                        cmdBuffer.Add( "from", userSending.UserName );
                        cmdBuffer.Add( "channel", cmd.GetItem( "channel" ) );
                        cmdBuffer.Add( "type", cmd.GetItem( "type" ) );
                        cmdBuffer.ExtraData = cmd.ExtraData;
                        tcp.SendDataToAll( cmdBuffer );
                    }
                    else
                    {
                        cmdBuffer.Intent = 131; //No session!
                        tcp.SendData( id, cmdBuffer );
                    }
                    break;


            }
        }
        private void tcp_ErrorEncounter(Exception ex)
        {
            Console.WriteLine();
            Console.WriteLine( "Error: " + ex.ToString() );
            Console.WriteLine();
        }
        private void tcp_lostConnection(EndPoint id)
        {
            Console.WriteLine( "Client disconnected, " + ( ( id != null ) ? id.ToString() : string.Empty ) );
            if ( id != null )
            {
                for ( int i = 0; i < this.people.Count; i++ )
                {
                    if ( id.Equals( this.people[i].Id ) )
                    {
                        this.people[i].Id = null;
                    }
                }
            }
            UpdateAllFriends();
        }
        private void tcp_onConnection(EndPoint id)
        {
            Console.WriteLine( "New client connected, " + ( ( id != null ) ? id.ToString() : string.Empty ) );
        }

        private bool _isDisposed;
        protected virtual void Dispose(bool disposing)
        {
            if ( !this._isDisposed )
            {
                if ( disposing )
                {
                    this.tcp.Dispose();
                }
                this._isDisposed = true;
            }
        }
        public void Dispose()
        {
            this.Dispose( true );
            GC.SuppressFinalize( this );
        }

    }

    public class ChatRoom
    {
        private string _name;
        private List<string> _people;

        public string Name
        {
            get
            {
                return this._name;
            }
        }

        public ChatRoom(string chatRoomName)
        {
            this._name = chatRoomName;
            this._people = new List<string>( 32 );
        }

        public void AddPerson(string name)
        {
            if ( name == null )
            {
                name = string.Empty;
            }
            bool added = false;
            name = name.Trim();
            for ( int i = 0; i < this._people.Count; i++ )
            {
                if ( name.Equals( this._people[i].Trim(), StringComparison.Ordinal ) )
                {
                    added = true;
                }
            }
            if ( !added )
            {
                this._people.Add( name );
            }
        }

        public void RemovePerson(string name)
        {
            this._people.Remove( name );
        }

        public string FindPerson(string name)
        {
            if ( name == null )
            {
                name = string.Empty;
            }
            name = name.Trim();
            for ( int i = 0; i < this._people.Count; i++ )
            {
                if ( name.Equals( this._people[i].Trim(), StringComparison.Ordinal ) )
                {
                    return this._people[i];
                }
            }
            return null;
        }

    }

    public class ChatClient
    {
        private string _userName;
        private string _password;
        private EndPoint _id;
        private Collection<string> _friends;

        public EndPoint Id
        {
            get
            {
                return this._id;
            }
            set
            {
                this._id = value;
            }
        }
        public string UserName
        {
            get
            {
                return this._userName;
            }
            set
            {
                this._userName = value;
            }
        }
        public void SetPassword(string value)
        {
            this._password = value;
        }
        public Collection<string> Friends
        {
            get
            {
                return this._friends;
            }
        }

        public ChatClient()
        {
            this._friends = new Collection<string>();
        }

        public bool TestPassword(string pass)
        {
            return string.Equals( this._password, pass );
        }
        public bool HasFriend(string name)
        {
            if ( string.IsNullOrEmpty( name ) )
                return false;
            name = name.Trim();
            for ( int i = 0; i < this._friends.Count; i++ )
            {
                if ( string.Equals( this._friends[i].Trim(), name, StringComparison.Ordinal ) )
                    return true;
            }
            return false;
        }
        public bool IsLoggedIn()
        {
            return this._id != null;
        }

        public static ChatClient FromEncryptedData(string encryptedValue)
        {
            ChatClient ret = new ChatClient();
            if ( encryptedValue != null )
            {
                string[] items = encryptedValue.Split( '.' );
                if ( items.Length == 3 )
                {
                    try
                    {
                        string foundUsername = Command.CurrentEncoding.GetString( Convert.FromBase64String( items[0] ) );
                        string foundPassword = Command.CurrentEncoding.GetString( Convert.FromBase64String( items[1] ) );
                        ret._userName = foundUsername;
                        ret._password = foundPassword;
                        string[] foundFriends = items[2].Split( '>' );
                        string tempString;
                        for ( int i = 0; i < foundFriends.Length; i++ )
                        {
                            if ( !string.IsNullOrEmpty( foundFriends[i] ) )
                            {
                                tempString = Command.CurrentEncoding.GetString( Convert.FromBase64String( foundFriends[i] ) );
                                ret._friends.Add( tempString );
                            }
                        }
                        return ret;
                    }
                    catch ( ArgumentException )
                    {
                        return null;
                    }
                }
                else
                {
                    return null;
                }
            }
            return null;
        }

        public string ToEncryptedData()
        {
            byte[] dataUsername = Command.CurrentEncoding.GetBytes( this._userName );
            byte[] dataPassword = Command.CurrentEncoding.GetBytes( this._password );

            string buffer = Convert.ToBase64String( dataUsername ) + "." + Convert.ToBase64String( dataPassword ) + ".";
            for ( int i = 0; i < this._friends.Count; i++ )
            {
                if ( i > 0 )
                {
                    buffer += ">";
                }
                buffer += Convert.ToBase64String( Command.CurrentEncoding.GetBytes( this._friends[i] ) );
            }
            return buffer;
        }

    }

}
